/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.swing.tree;

import java.util.*;
import java.beans.ConstructorProperties;
import java.io.*;
import javax.swing.event.*;

/**
 * A simple tree data model that uses TreeNodes. For further information and examples that use
 * DefaultTreeModel, see <a href="https://docs.oracle.com/javase/tutorial/uiswing/components/tree.html">How
 * to Use Trees</a> in <em>The Java Tutorial.</em> <p> <strong>Warning:</strong> Serialized objects
 * of this class will not be compatible with future Swing releases. The current serialization
 * support is appropriate for short term storage or RMI between applications running the same
 * version of Swing.  As of 1.4, support for long term storage of all JavaBeans&trade; has been
 * added to the <code>java.beans</code> package. Please see {@link java.beans.XMLEncoder}.
 *
 * @author Rob Davis
 * @author Ray Ryan
 * @author Scott Violet
 */
public class DefaultTreeModel implements Serializable, TreeModel {

  /**
   * Root of the tree.
   */
  protected TreeNode root;
  /**
   * Listeners.
   */
  protected EventListenerList listenerList = new EventListenerList();
  /**
   * Determines how the <code>isLeaf</code> method figures
   * out if a node is a leaf node. If true, a node is a leaf
   * node if it does not allow children. (If it allows
   * children, it is not a leaf node, even if no children
   * are present.) That lets you distinguish between <i>folder</i>
   * nodes and <i>file</i> nodes in a file system, for example.
   * <p>
   * If this value is false, then any node which has no
   * children is a leaf node, and any node may acquire
   * children.
   *
   * @see TreeNode#getAllowsChildren
   * @see TreeModel#isLeaf
   * @see #setAsksAllowsChildren
   */
  protected boolean asksAllowsChildren;


  /**
   * Creates a tree in which any node can have children.
   *
   * @param root a TreeNode object that is the root of the tree
   * @see #DefaultTreeModel(TreeNode, boolean)
   */
  @ConstructorProperties({"root"})
  public DefaultTreeModel(TreeNode root) {
    this(root, false);
  }

  /**
   * Creates a tree specifying whether any node can have children,
   * or whether only certain nodes can have children.
   *
   * @param root a TreeNode object that is the root of the tree
   * @param asksAllowsChildren a boolean, false if any node can have children, true if each node is
   * asked to see if it can have children
   * @see #asksAllowsChildren
   */
  public DefaultTreeModel(TreeNode root, boolean asksAllowsChildren) {
    super();
    this.root = root;
    this.asksAllowsChildren = asksAllowsChildren;
  }

  /**
   * Sets whether or not to test leafness by asking getAllowsChildren()
   * or isLeaf() to the TreeNodes.  If newvalue is true, getAllowsChildren()
   * is messaged, otherwise isLeaf() is messaged.
   */
  public void setAsksAllowsChildren(boolean newValue) {
    asksAllowsChildren = newValue;
  }

  /**
   * Tells how leaf nodes are determined.
   *
   * @return true if only nodes which do not allow children are leaf nodes, false if nodes which
   * have no children (even if allowed) are leaf nodes
   * @see #asksAllowsChildren
   */
  public boolean asksAllowsChildren() {
    return asksAllowsChildren;
  }

  /**
   * Sets the root to <code>root</code>. A null <code>root</code> implies
   * the tree is to display nothing, and is legal.
   */
  public void setRoot(TreeNode root) {
    Object oldRoot = this.root;
    this.root = root;
    if (root == null && oldRoot != null) {
      fireTreeStructureChanged(this, null);
    } else {
      nodeStructureChanged(root);
    }
  }

  /**
   * Returns the root of the tree.  Returns null only if the tree has
   * no nodes.
   *
   * @return the root of the tree
   */
  public Object getRoot() {
    return root;
  }

  /**
   * Returns the index of child in parent.
   * If either the parent or child is <code>null</code>, returns -1.
   *
   * @param parent a note in the tree, obtained from this data source
   * @param child the node we are interested in
   * @return the index of the child in the parent, or -1 if either the parent or the child is
   * <code>null</code>
   */
  public int getIndexOfChild(Object parent, Object child) {
    if (parent == null || child == null) {
      return -1;
    }
    return ((TreeNode) parent).getIndex((TreeNode) child);
  }

  /**
   * Returns the child of <I>parent</I> at index <I>index</I> in the parent's
   * child array.  <I>parent</I> must be a node previously obtained from
   * this data source. This should not return null if <i>index</i>
   * is a valid index for <i>parent</i> (that is <i>index</i> &gt;= 0 &amp;&amp;
   * <i>index</i> &lt; getChildCount(<i>parent</i>)).
   *
   * @param parent a node in the tree, obtained from this data source
   * @return the child of <I>parent</I> at index <I>index</I>
   */
  public Object getChild(Object parent, int index) {
    return ((TreeNode) parent).getChildAt(index);
  }

  /**
   * Returns the number of children of <I>parent</I>.  Returns 0 if the node
   * is a leaf or if it has no children.  <I>parent</I> must be a node
   * previously obtained from this data source.
   *
   * @param parent a node in the tree, obtained from this data source
   * @return the number of children of the node <I>parent</I>
   */
  public int getChildCount(Object parent) {
    return ((TreeNode) parent).getChildCount();
  }

  /**
   * Returns whether the specified node is a leaf node.
   * The way the test is performed depends on the
   * <code>askAllowsChildren</code> setting.
   *
   * @param node the node to check
   * @return true if the node is a leaf node
   * @see #asksAllowsChildren
   * @see TreeModel#isLeaf
   */
  public boolean isLeaf(Object node) {
    if (asksAllowsChildren) {
      return !((TreeNode) node).getAllowsChildren();
    }
    return ((TreeNode) node).isLeaf();
  }

  /**
   * Invoke this method if you've modified the {@code TreeNode}s upon which
   * this model depends. The model will notify all of its listeners that the
   * model has changed.
   */
  public void reload() {
    reload(root);
  }

  /**
   * This sets the user object of the TreeNode identified by path
   * and posts a node changed.  If you use custom user objects in
   * the TreeModel you're going to need to subclass this and
   * set the user object of the changed node to something meaningful.
   */
  public void valueForPathChanged(TreePath path, Object newValue) {
    MutableTreeNode aNode = (MutableTreeNode) path.getLastPathComponent();

    aNode.setUserObject(newValue);
    nodeChanged(aNode);
  }

  /**
   * Invoked this to insert newChild at location index in parents children.
   * This will then message nodesWereInserted to create the appropriate
   * event. This is the preferred way to add children as it will create
   * the appropriate event.
   */
  public void insertNodeInto(MutableTreeNode newChild,
      MutableTreeNode parent, int index) {
    parent.insert(newChild, index);

    int[] newIndexs = new int[1];

    newIndexs[0] = index;
    nodesWereInserted(parent, newIndexs);
  }

  /**
   * Message this to remove node from its parent. This will message
   * nodesWereRemoved to create the appropriate event. This is the
   * preferred way to remove a node as it handles the event creation
   * for you.
   */
  public void removeNodeFromParent(MutableTreeNode node) {
    MutableTreeNode parent = (MutableTreeNode) node.getParent();

    if (parent == null) {
      throw new IllegalArgumentException("node does not have a parent.");
    }

    int[] childIndex = new int[1];
    Object[] removedArray = new Object[1];

    childIndex[0] = parent.getIndex(node);
    parent.remove(childIndex[0]);
    removedArray[0] = node;
    nodesWereRemoved(parent, childIndex, removedArray);
  }

  /**
   * Invoke this method after you've changed how node is to be
   * represented in the tree.
   */
  public void nodeChanged(TreeNode node) {
    if (listenerList != null && node != null) {
      TreeNode parent = node.getParent();

      if (parent != null) {
        int anIndex = parent.getIndex(node);
        if (anIndex != -1) {
          int[] cIndexs = new int[1];

          cIndexs[0] = anIndex;
          nodesChanged(parent, cIndexs);
        }
      } else if (node == getRoot()) {
        nodesChanged(node, null);
      }
    }
  }

  /**
   * Invoke this method if you've modified the {@code TreeNode}s upon which
   * this model depends. The model will notify all of its listeners that the
   * model has changed below the given node.
   *
   * @param node the node below which the model has changed
   */
  public void reload(TreeNode node) {
    if (node != null) {
      fireTreeStructureChanged(this, getPathToRoot(node), null, null);
    }
  }

  /**
   * Invoke this method after you've inserted some TreeNodes into
   * node.  childIndices should be the index of the new elements and
   * must be sorted in ascending order.
   */
  public void nodesWereInserted(TreeNode node, int[] childIndices) {
    if (listenerList != null && node != null && childIndices != null
        && childIndices.length > 0) {
      int cCount = childIndices.length;
      Object[] newChildren = new Object[cCount];

      for (int counter = 0; counter < cCount; counter++) {
        newChildren[counter] = node.getChildAt(childIndices[counter]);
      }
      fireTreeNodesInserted(this, getPathToRoot(node), childIndices,
          newChildren);
    }
  }

  /**
   * Invoke this method after you've removed some TreeNodes from
   * node.  childIndices should be the index of the removed elements and
   * must be sorted in ascending order. And removedChildren should be
   * the array of the children objects that were removed.
   */
  public void nodesWereRemoved(TreeNode node, int[] childIndices,
      Object[] removedChildren) {
    if (node != null && childIndices != null) {
      fireTreeNodesRemoved(this, getPathToRoot(node), childIndices,
          removedChildren);
    }
  }

  /**
   * Invoke this method after you've changed how the children identified by
   * childIndicies are to be represented in the tree.
   */
  public void nodesChanged(TreeNode node, int[] childIndices) {
    if (node != null) {
      if (childIndices != null) {
        int cCount = childIndices.length;

        if (cCount > 0) {
          Object[] cChildren = new Object[cCount];

          for (int counter = 0; counter < cCount; counter++) {
            cChildren[counter] = node.getChildAt
                (childIndices[counter]);
          }
          fireTreeNodesChanged(this, getPathToRoot(node),
              childIndices, cChildren);
        }
      } else if (node == getRoot()) {
        fireTreeNodesChanged(this, getPathToRoot(node), null, null);
      }
    }
  }

  /**
   * Invoke this method if you've totally changed the children of
   * node and its children's children...  This will post a
   * treeStructureChanged event.
   */
  public void nodeStructureChanged(TreeNode node) {
    if (node != null) {
      fireTreeStructureChanged(this, getPathToRoot(node), null, null);
    }
  }

  /**
   * Builds the parents of node up to and including the root node,
   * where the original node is the last element in the returned array.
   * The length of the returned array gives the node's depth in the
   * tree.
   *
   * @param aNode the TreeNode to get the path for
   */
  public TreeNode[] getPathToRoot(TreeNode aNode) {
    return getPathToRoot(aNode, 0);
  }

  /**
   * Builds the parents of node up to and including the root node,
   * where the original node is the last element in the returned array.
   * The length of the returned array gives the node's depth in the
   * tree.
   *
   * @param aNode the TreeNode to get the path for
   * @param depth an int giving the number of steps already taken towards the root (on recursive
   * calls), used to size the returned array
   * @return an array of TreeNodes giving the path from the root to the specified node
   */
  protected TreeNode[] getPathToRoot(TreeNode aNode, int depth) {
    TreeNode[] retNodes;
    // This method recurses, traversing towards the root in order
    // size the array. On the way back, it fills in the nodes,
    // starting from the root and working back to the original node.

        /* Check for null, in case someone passed in a null node, or
           they passed in an element that isn't rooted at root. */
    if (aNode == null) {
      if (depth == 0) {
        return null;
      } else {
        retNodes = new TreeNode[depth];
      }
    } else {
      depth++;
      if (aNode == root) {
        retNodes = new TreeNode[depth];
      } else {
        retNodes = getPathToRoot(aNode.getParent(), depth);
      }
      retNodes[retNodes.length - depth] = aNode;
    }
    return retNodes;
  }

  //
  //  Events
  //

  /**
   * Adds a listener for the TreeModelEvent posted after the tree changes.
   *
   * @param l the listener to add
   * @see #removeTreeModelListener
   */
  public void addTreeModelListener(TreeModelListener l) {
    listenerList.add(TreeModelListener.class, l);
  }

  /**
   * Removes a listener previously added with <B>addTreeModelListener()</B>.
   *
   * @param l the listener to remove
   * @see #addTreeModelListener
   */
  public void removeTreeModelListener(TreeModelListener l) {
    listenerList.remove(TreeModelListener.class, l);
  }

  /**
   * Returns an array of all the tree model listeners
   * registered on this model.
   *
   * @return all of this model's <code>TreeModelListener</code>s or an empty array if no tree model
   * listeners are currently registered
   * @see #addTreeModelListener
   * @see #removeTreeModelListener
   * @since 1.4
   */
  public TreeModelListener[] getTreeModelListeners() {
    return listenerList.getListeners(TreeModelListener.class);
  }

  /**
   * Notifies all listeners that have registered interest for
   * notification on this event type.  The event instance
   * is lazily created using the parameters passed into
   * the fire method.
   *
   * @param source the source of the {@code TreeModelEvent}; typically {@code this}
   * @param path the path to the parent of the nodes that changed; use {@code null} to identify the
   * root has changed
   * @param childIndices the indices of the changed elements
   * @param children the changed elements
   */
  protected void fireTreeNodesChanged(Object source, Object[] path,
      int[] childIndices,
      Object[] children) {
    // Guaranteed to return a non-null array
    Object[] listeners = listenerList.getListenerList();
    TreeModelEvent e = null;
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == TreeModelListener.class) {
        // Lazily create the event:
        if (e == null) {
          e = new TreeModelEvent(source, path,
              childIndices, children);
        }
        ((TreeModelListener) listeners[i + 1]).treeNodesChanged(e);
      }
    }
  }

  /**
   * Notifies all listeners that have registered interest for
   * notification on this event type.  The event instance
   * is lazily created using the parameters passed into
   * the fire method.
   *
   * @param source the source of the {@code TreeModelEvent}; typically {@code this}
   * @param path the path to the parent the nodes were added to
   * @param childIndices the indices of the new elements
   * @param children the new elements
   */
  protected void fireTreeNodesInserted(Object source, Object[] path,
      int[] childIndices,
      Object[] children) {
    // Guaranteed to return a non-null array
    Object[] listeners = listenerList.getListenerList();
    TreeModelEvent e = null;
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == TreeModelListener.class) {
        // Lazily create the event:
        if (e == null) {
          e = new TreeModelEvent(source, path,
              childIndices, children);
        }
        ((TreeModelListener) listeners[i + 1]).treeNodesInserted(e);
      }
    }
  }

  /**
   * Notifies all listeners that have registered interest for
   * notification on this event type.  The event instance
   * is lazily created using the parameters passed into
   * the fire method.
   *
   * @param source the source of the {@code TreeModelEvent}; typically {@code this}
   * @param path the path to the parent the nodes were removed from
   * @param childIndices the indices of the removed elements
   * @param children the removed elements
   */
  protected void fireTreeNodesRemoved(Object source, Object[] path,
      int[] childIndices,
      Object[] children) {
    // Guaranteed to return a non-null array
    Object[] listeners = listenerList.getListenerList();
    TreeModelEvent e = null;
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == TreeModelListener.class) {
        // Lazily create the event:
        if (e == null) {
          e = new TreeModelEvent(source, path,
              childIndices, children);
        }
        ((TreeModelListener) listeners[i + 1]).treeNodesRemoved(e);
      }
    }
  }

  /**
   * Notifies all listeners that have registered interest for
   * notification on this event type.  The event instance
   * is lazily created using the parameters passed into
   * the fire method.
   *
   * @param source the source of the {@code TreeModelEvent}; typically {@code this}
   * @param path the path to the parent of the structure that has changed; use {@code null} to
   * identify the root has changed
   * @param childIndices the indices of the affected elements
   * @param children the affected elements
   */
  protected void fireTreeStructureChanged(Object source, Object[] path,
      int[] childIndices,
      Object[] children) {
    // Guaranteed to return a non-null array
    Object[] listeners = listenerList.getListenerList();
    TreeModelEvent e = null;
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == TreeModelListener.class) {
        // Lazily create the event:
        if (e == null) {
          e = new TreeModelEvent(source, path,
              childIndices, children);
        }
        ((TreeModelListener) listeners[i + 1]).treeStructureChanged(e);
      }
    }
  }

  /**
   * Notifies all listeners that have registered interest for
   * notification on this event type.  The event instance
   * is lazily created using the parameters passed into
   * the fire method.
   *
   * @param source the source of the {@code TreeModelEvent}; typically {@code this}
   * @param path the path to the parent of the structure that has changed; use {@code null} to
   * identify the root has changed
   */
  private void fireTreeStructureChanged(Object source, TreePath path) {
    // Guaranteed to return a non-null array
    Object[] listeners = listenerList.getListenerList();
    TreeModelEvent e = null;
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == TreeModelListener.class) {
        // Lazily create the event:
        if (e == null) {
          e = new TreeModelEvent(source, path);
        }
        ((TreeModelListener) listeners[i + 1]).treeStructureChanged(e);
      }
    }
  }

  /**
   * Returns an array of all the objects currently registered as <code><em>Foo</em>Listener</code>s
   * upon this model. <code><em>Foo</em>Listener</code>s are registered using the
   * <code>add<em>Foo</em>Listener</code> method.
   *
   * <p>
   *
   * You can specify the <code>listenerType</code> argument with a class literal, such as
   * <code><em>Foo</em>Listener.class</code>. For example, you can query a
   * <code>DefaultTreeModel</code> <code>m</code> for its tree model listeners with the following
   * code:
   *
   * <pre>TreeModelListener[] tmls = (TreeModelListener[])(m.getListeners(TreeModelListener.class));</pre>
   *
   * If no such listeners exist, this method returns an empty array.
   *
   * @param listenerType the type of listeners requested; this parameter should specify an interface
   * that descends from <code>java.util.EventListener</code>
   * @return an array of all objects registered as <code><em>Foo</em>Listener</code>s on this
   * component, or an empty array if no such listeners have been added
   * @throws ClassCastException if <code>listenerType</code> doesn't specify a class or interface
   * that implements <code>java.util.EventListener</code>
   * @see #getTreeModelListeners
   * @since 1.3
   */
  public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
    return listenerList.getListeners(listenerType);
  }

  // Serialization support.
  private void writeObject(ObjectOutputStream s) throws IOException {
    Vector<Object> values = new Vector<Object>();

    s.defaultWriteObject();
    // Save the root, if its Serializable.
    if (root != null && root instanceof Serializable) {
      values.addElement("root");
      values.addElement(root);
    }
    s.writeObject(values);
  }

  private void readObject(ObjectInputStream s)
      throws IOException, ClassNotFoundException {
    s.defaultReadObject();

    Vector values = (Vector) s.readObject();
    int indexCounter = 0;
    int maxCounter = values.size();

    if (indexCounter < maxCounter && values.elementAt(indexCounter).
        equals("root")) {
      root = (TreeNode) values.elementAt(++indexCounter);
      indexCounter++;
    }
  }


} // End of class DefaultTreeModel
